# Cue file parser
# v 0.1 (2007-06-27)

class _track:
  name = unicode('')
  performer = unicode('')
  index_ms = 0
  index_samp = 0
  duration = 0
  sduration = ''
  sindex = ''
  samples = 0
  number = 0
  
  def __init__(self):
    pass
    
class _remark:
  key = unicode('')
  val = unicode('')
  
  def __init__(self):
    pass
    
class CueParser:

  title = unicode('')
  performer = unicode('')
  year = unicode('')
  file = unicode('')
  numtracks = 0
  sr = 0
  duration = 0
  sduration = ''
  samples = 0
  tracks = [ ]
  ms2samp = 0
  remarks = [ ]

  def __init__(self):
    pass
    
  def getSoundFileFromCue(self, cuefile):
    """If all you have is the CUE Sheet, you need the name of the associated
        sound file, so you can get numsamples and fs."""
    filelocation = cuefile.find("FILE \"")
    filename = cuefile[filelocation+6:cuefile.find("\"",filelocation+6)].strip("\" ")
    return filename

  def writeCUESheet(self,filepath):
    """Writes a formatted CUE sheet to the specified file (overwrites if necessary!)"""

    cuesheet = self.generateCUESheet()
    try:
      import codecs
      fin = None
      fin = codecs.open(filepath, encoding='utf8', mode='w')
      fin.write(cuesheet)
    except:
      print "error writing file"
    finally:
      if fin != None:
        fin.close()
    
  def generateCUESheet(self):
    """Generates a formatted CUE sheet, and returns it as a string"""
    
    CUESheet = u''
    for j in self.remarks:
      CUESheet = CUESheet + "REM " + j.key + " \"" + j.val + "\"\n"
    CUESheet = CUESheet + "PERFORMER \"" + self.performer + "\"\n"
    CUESheet = CUESheet + "TITLE \"" + self.title + "\"\n"
    CUESheet = CUESheet + "FILE \"" + self.file + "\" WAVE\n"
    compilation = False
    for j in self.tracks:
      if j.performer != self.performer:
        compilation = True
    for j in self.tracks:
      CUESheet = CUESheet + "  TRACK " + str(j.number) + " AUDIO\n"
      CUESheet = CUESheet + "    INDEX 01 " + j.sindex + "\n"
      CUESheet = CUESheet + "    TITLE \"" + j.name + "\"\n"
      if compilation:
        CUESheet = CUESheet + "    PERFORMER \"" + j.performer + "\"\n"
    return CUESheet

  def getMetadataFromCDDB(self, DISCID, CDDB, samples=None):
    """Generates a CUE object from CDDB info"""

    performertitle = CDDB['DTITLE'].split("/")
    self.title = performertitle[1].strip()
    self.performer = performertitle[0].strip()
    self.year = CDDB['DYEAR']
    self.file = ''

    if samples == None:
      # Just an estimate, rounded to nearest second! 
      # But if you are just writing the CUE file, this is irrelevant
      self.samples = DISCID[len(DISCID)-1] * 44100
    else:
      self.samples = samples
    self.sr = 44100
    self.duration = (self.samples / self.sr) * 1000
    self.numtracks = DISCID[1]
    self.tracks = [ ]
    self.ms2samp = self.sr/1000
    self.remarks = [ ]
    if int(CDDB["NUMREMARKS"]) > 0:
      for i in range(0,int(CDDB["NUMREMARKS"])):
        thisremark = _remark()
        remark = CDDB["REMARK" + str(i)]
        thisremark.key = remark.split('=')[0]
        thisremark.val = remark.split('=')[1]
        self.remarks.append(thisremark)

    for i in range(0,self.numtracks):
      thistrack = _track()
      thistrack.name = CDDB['TTITLE' + `i`]
      if CDDB.has_key('TARTIST' + `i`):
        thistrack.performer = CDDB['TARTIST' + `i`]
      else:
        thistrack.performer = self.performer
      thistrack.index_ms = round((DISCID[i+2] - DISCID[2]) / .075)
      thistrack.number = i+1
      self.tracks.append(thistrack)
    
    self._calcTrackValues()

  def getMetadataFromMutagen(self, metadata):
    """Generates a CUE object from a Mutagen metadata object"""
    
    import os
    if metadata.has_key('ALBUM'):
      self.title = metadata["ALBUM"][0]
    else:
      self.title = ''
    if metadata.has_key('ARTIST'):
      self.performer = metadata["ARTIST"][0]
    else:
      self.performer = ''
    if metadata.has_key('YEAR'):
      self.year = metadata["YEAR"][0]
    else:
      if metadata.has_key('DATE'):
        self.year = metadata["DATE"][0]
      else:
        self.year = ''
    if metadata.has_key('FILENAME'):
      self.file = os.path.basename(metadata['FILENAME'][0])
    else:
      self.file = ''

    self.samples = metadata.info.total_samples
    self.sr = metadata.info.sample_rate
    self.duration = (self.samples / self.sr) * 1000
    self.numtracks = len(metadata.cuesheet.tracks)-1
    self.tracks = [ ]
    self.ms2samp = self.sr/1000
    self.remarks = [ ]
    if metadata.has_key('remark'):
      thisremarks = metadata.get('remark')
      for remark in thisremarks:
        thisremark = _remark()
        thisremark.key = remark.split('=')[0]
        thisremark.val = remark.split('=')[1]
        self.remarks.append(thisremark)
      
    for i in range(0,self.numtracks):
      thistrack = _track()
      if metadata.has_key('TITLE[' + str(i+1) + ']'):
        thistrack.name = metadata['TITLE[' + str(i+1) + ']'][0]
      else:
        thistrack.name = 'Track ' + str(i+1)
      if metadata.has_key('PERFORMER[' + str(i+1) + ']'):
        thistrack.performer = metadata['PERFORMER[' + str(i+1) + ']'][0]
      else:
        thistrack.performer = self.performer
      thistrack.index_ms = metadata.cuesheet.tracks[i].start_offset /self.ms2samp
      thistrack.number = int(metadata.cuesheet.tracks[i].track_number)
      self.tracks.append(thistrack)
    
    self._calcTrackValues()

  def getMetadataFromCue(self, cuefile, samples, fs=44100):
    """Generates a CUE object by parsing a CUE sheet string"""
    
    import os, string
    self.title = ''
    self.performer = ''
    self.year = ''
    self.file = ''
    self.numtracks = 0;
    self.sr = fs
    self.duration = 0
    self.samples = samples
    self.tracks = [ ]
    self.ms2samp = 0
    self.remarks = [ ]
    files = cuefile.split("FILE")
    if len(files) <= 2:
      thisremarks = files[0].splitlines()
      for i in thisremarks:
        thisremark = _remark()
        thisline = i.strip()
        if thisline.startswith("TITLE") != 0:
          self.title = thisline[6:len(thisline)].strip("\" ")
        elif thisline.startswith("PERFORMER") != 0:
          self.performer = thisline[10:len(thisline)].strip("\" ")
        elif thisline.startswith("REM YEAR") != 0:
          self.year = thisline[9:len(thisline)].strip("\" ")
        elif thisline.startswith("REM DATE") != 0:
          self.year = thisline[9:len(thisline)].strip("\" ")
        else:
          thiscomment = thisline.split(" ",2)
          thisremark.key = thiscomment[1].strip("\" ")
          thisremark.val = thiscomment[2].strip("\" ")
          self.remarks.append(thisremark)

      strtracks = files[1].split("TRACK ")
      tmpfilenameline = strtracks.pop(0)
      self.file = tmpfilenameline[1:tmpfilenameline.rfind("\"")+1].strip("\" ")
      self.numtracks = len(strtracks)
      for i in strtracks:
        gotname = 0
        gotperf = 0
        gottime = 0
        tracklines = i.splitlines()
        thistrack = _track()
        thistrack.number = int(i[0:2])
        for j in tracklines:
          thisline = j.strip()
          if thisline.startswith("TITLE") != 0:
            thistrack.name = thisline[6:len(thisline)].strip("\" ")
            gotname = 1
          elif thisline.startswith("INDEX 01") != 0:
            thisindex = thisline[9:len(thisline)].strip("\" ")
            thisvals = thisindex.split(":")
            thistrack.index_ms = (int(thisvals[0]) * 60000) + (int(thisvals[1]) * 1000) + (int(int(thisvals[2]) / 75 ) * 1000)
            gottime = 1
          elif thisline.startswith("PERFORMER") != 0:
            thistrack.performer = thisline[10:len(thisline)].strip("\" ")
            gotperf = 1
          if gotname== 0:
            thistrack.name = 'Track ' + i[0:2]
          if gotperf == 0:
            thistrack.performer = self.performer
          if gottime == 0:
            thistrack.time = ''
            # error: Track time not defined

        self.tracks.append(thistrack)

    else:
      print "Parser can only handle cuesheets associated with a single soundfile"
      #error: Parser can only handle cuesheets associated with a single soundfile

    self.ms2samp = self.sr/1000
    self.duration = self.samples/self.ms2samp
    self._calcTrackValues()
    
  def _calcTrackValues(self):
    """Helper function for the repetitive task of computing values associated with time, duration, etc. for each track"""

    self.sduration = self.getTimeString_MS(self.duration)
    self.tracks[len(self.tracks) - 1].duration = self.duration - self.tracks[len(self.tracks) - 2].index_ms
    self.tracks[len(self.tracks) - 1].samples = self.samples - self.tracks[len(self.tracks) - 2].index_samp
    self.tracks[len(self.tracks) - 1].index_samp = self.tracks[len(self.tracks) - 1].index_ms * self.ms2samp
    self.tracks[len(self.tracks) - 1].sduration = self.getTimeString_MS(self.tracks[len(self.tracks) - 1].duration)
    self.tracks[len(self.tracks) - 1].sindex = self.getTimeString_MSR(self.tracks[len(self.tracks) - 1].index_ms)
    for i in range(0, self.numtracks - 1):
      self.tracks[i].duration = self.tracks[i+1].index_ms - self.tracks[i].index_ms
      self.tracks[i].duration = self.tracks[i+1].index_ms - self.tracks[i].index_ms
      self.tracks[i].samples = self.tracks[i].duration * self.ms2samp
      self.tracks[i].index_samp = self.tracks[i].index_ms * self.ms2samp
      self.tracks[i].sduration = self.getTimeString_MS(self.tracks[i].duration)
      self.tracks[i].sindex = self.getTimeString_MSR(self.tracks[i].index_ms)

  def getTimeString_MS(self,Time_ms):
    """Helper function to generate track duration times as strings
    
    Generates a string containing time in mm:ss format
    mm = # minutes, ss = # seconds"""
    
    min = int(Time_ms / 60000)
    sec = int((Time_ms - (min *60000)) / 1000)
    if min < 10:
      dm = "0" + str(min)
    else:
      dm = str(min)
    if sec < 10:
      ds = "0" + str(sec)
    else:
      ds = str(sec)
    sduration = dm+":"+ds
    return sduration

  def getTimeString_MSR(self,Time_ms):
    """Helper function to generate CUE sheet index times as strings 
    
    Generates a string containing time in mm:ss:rr format
    mm = # minutes, ss = # seconds and rr = # of CD sectors (1/75 minute)"""
    
    thissectors = (Time_ms /1000) *75
    thismin = int((thissectors / 75) / 60)
    thissec = int((thissectors / 75) % 60)
    thisrem = int(thissectors % 75)
    if thismin < 10: 
      sthismin = '0' + str(thismin)
    else:
      sthismin = str(thismin)
    if thissec < 10: 
      sthissec = '0' + str(thissec)
    else:
      sthissec = str(thissec)
    if thisrem < 10: 
      sthisrem = '0' + str(thisrem)
    else:
      sthisrem = str(thisrem)
    stime = sthismin+":"+sthissec+":"+sthisrem
    return stime
